package com.zodiac.alioth.common.api;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zodiac.alioth.common.api.annotation.ApiParam;
import com.zodiac.alioth.common.api.annotation.ApiParamBody;
import com.zodiac.alioth.common.model.vo.ApiResponse;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.util.ReflectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by well on 2014/7/29.
 */
public class ApiMethodInvoker {

    public ApiResponse<?> invoke(ApiRequest apiRequest, ApiHandler apiHandler) throws IllegalAccessException, ParseException, InstantiationException {
        Object[] args = resolveArguments(apiHandler, apiRequest);
        Object res ;
        if (args == null) {
            res = ReflectionUtils.invokeMethod(apiHandler.getHandleMethod(), apiRequest.getApiHandler().getHandlerObject());
        } else {
            res = ReflectionUtils.invokeMethod(apiHandler.getHandleMethod(), apiRequest.getApiHandler().getHandlerObject(), args);
        }
        return (ApiResponse<?>)res;
    }

    private Object[] resolveArguments(ApiHandler apiHandler, ApiRequest apiRequest) throws IllegalAccessException, ParseException, InstantiationException {
        Class<?>[] paramTypes = apiHandler.getHandleMethod().getParameterTypes();
        if (paramTypes.length == 0) {
            return null;
        }
        LocalVariableTableParameterNameDiscoverer parameterUtils = new LocalVariableTableParameterNameDiscoverer();
        Method handleMethod = apiHandler.getHandleMethod();
        String[] parameterNames = parameterUtils.getParameterNames(handleMethod);
        Annotation[][] parameterAnnotations = apiHandler.getHandleMethod().getParameterAnnotations();
        Object[] args = new Object[paramTypes.length];
        for (int i = 0; i < args.length; i++) {
            Class<?> param = paramTypes[i];
            if(param == HttpServletRequest.class){
                Object val = apiRequest.getHttpServletRequest();
                args[i] = val;
                continue;
            }
            if(param == HttpServletResponse.class){
                Object val = apiRequest.getHttpServletResponse();
                args[i] = val;
                continue;
            }
            appendJsonBody(apiRequest);
            Annotation[] parameterAnnotation = parameterAnnotations[i];
            ApiParam apiParam = getApiParamAnno(parameterAnnotation);
            ApiParamBody apiParamBody = getApiParamBodyAnno(parameterAnnotation);
            if (apiParamBody != null && apiRequest.getBody() != null) {
                Object obj = JSON.parse(apiRequest.getBody());
                if (obj instanceof JSONObject) {
                    args[i] = JSON.parseObject(apiRequest.getBody(), param);
                } else if (obj instanceof JSONArray) {
                    args[i] = JSONArray.parseArray(apiRequest.getBody(), Object.class);
                }
                continue;
            }
            if (apiParam != null) {
                String paramName = apiParam.name();
                Object val = apiRequest.getRequestParams().get(paramName);
                args[i] = ReflectTool.resolveField(param, apiRequest.getRequestParams(), val);
            } else {
                assert parameterNames != null;
                String paramName = parameterNames[i];
                Object val = apiRequest.getRequestParams().get(paramName);
                args[i] = ReflectTool.resolveField(param, apiRequest.getRequestParams(), val);
            }
        }
        return args;
    }

    private void appendJsonBody(ApiRequest apiRequest) {
        if (apiRequest == null || apiRequest.getBody() == null) {
            return;
        }
        String body = apiRequest.getBody();
        Object obj = JSON.parse(body);
        if (null == obj) {
            return;
        }
        if (obj instanceof JSONArray) {
            return;
        }
        JSONObject jsonObject = JSON.parseObject(body);
        if (jsonObject == null) {
            return;
        }
        Map<String, Object> requestParams = apiRequest.getRequestParams();
        if (requestParams == null) {
            requestParams = new HashMap<>();
            apiRequest.setRequestParams(requestParams);
        }
        for (Map.Entry<String, Object> next : jsonObject.entrySet()) {
            requestParams.put(next.getKey(), next.getValue());
        }
    }

    private ApiParam getApiParamAnno(Annotation[] parameterAnnotation) {
        if (parameterAnnotation == null) {
            return null;
        }
        for (Annotation annotation : parameterAnnotation) {
            if (annotation instanceof ApiParam) {
                return (ApiParam) annotation;
            }
        }
        return null;
    }

    private ApiParamBody getApiParamBodyAnno(Annotation[] parameterAnnotation) {
        if (parameterAnnotation == null) {
            return null;
        }
        for (Annotation annotation : parameterAnnotation) {
            if (annotation instanceof ApiParamBody) {
                return (ApiParamBody) annotation;
            }
        }
        return null;
    }


}
